﻿using FySystem.AssistantServices;
using FySystem.Net.FyNetConnection.Src;
using FySystem.Net.FyNetConnection.Src.SubPacking;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FySystem.Net.FyNetConnection
{
    public enum SubPackageMode
    {
        /// <summary>
        /// 不进行分包，收到什么就是什么
        /// </summary>
        None,
        /// <summary>
        /// 内部方式，使用EndFlag作为数据结束标记，可靠性较差
        /// </summary>
        InnerMode,
        /// <summary>
        /// 自定义模式，自己实现PackingFun委托来分包
        /// </summary>
        CustomMode,
        /// <summary>
        /// 第一版本的专用方式
        /// </summary>
        FyModeV1
    }

    public enum PackingMode
    {
        /// <summary>
        /// 直接将数据进行发送，不做任何处理
        /// </summary>
        None = 0,
        /// <summary>
        /// 按照第一版本的专用模式对数据进行打包
        /// </summary>
        FyModeV1 = 1
    }

    public class FyServer : IAssistantService
    {
        private Action<IAssistantService> _configFun;

        private ManualResetEvent _allDone = new ManualResetEvent(false);

        public delegate StateObjectBase OnAcceptHandler(FyServer fyServer, Socket handler);
        public delegate List<byte[]> OnPacking(int bytesRead, StateObjectBase stateObjectBase);

        /// <summary>
        /// 配置
        /// </summary>
        /// <param name="configFun"></param>
        public void Configure(Action<IAssistantService> configFun)
        {
            _configFun = configFun;
        }

        /// <summary>
        /// 内部调用
        /// </summary>
        public void InvokeConfigure()
        {
            try
            {
                _configFun?.Invoke(this);
            }
            catch (Exception ex)
            {
                FyLogContext.Write(ex);
            }
        }

        #region 属性
        /// <summary>
        /// 端口
        /// </summary>
        public int Port { get; set; } = 20000;

        /// <summary>
        /// 监听网卡IP
        /// </summary>
        public IPAddress IpAddress { get; set; } = IPAddress.Any;

        /// <summary>
        /// 分包模式，默认为None
        /// </summary>
        public SubPackageMode SubPackageMode { get; set; } = SubPackageMode.None;

        /// <summary>
        /// 消息末尾结束标记，如果不设置，则收到一条是一条，不对粘包进行处理
        /// </summary>
        public byte[] EndFlag { get; set; } = null;

        /// <summary>
        /// 如果收到了数据要自己处理分包，则在该委托中处理
        /// </summary>
        public OnPacking PackingFun { get; set; }

        /// <summary>
        /// 链接连入回调
        /// </summary>
        public OnAcceptHandler AcceptCallback { get; set; }

        /// <summary>
        /// 连接连入，其它代码都执行结束之后的回调
        /// </summary>
        public Action<FyServer, StateObjectBase> AcceptedCallback { get; set; }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        public Action<FyServer, StateObjectBase, byte[]> ReceiveCallback { get; set; }

        /// <summary>
        /// 出错回调
        /// </summary>
        public Action<Exception, Socket> ExceptionCallback { get; set; }

        /// <summary>
        /// 接收客户端数据的时候的缓存大小
        /// </summary>
        public int ReceiveBufferSize { get; set; } = 1024;
        #endregion

        #region 设置
        /// <summary>
        /// 设置端口
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public FyServer SetPort(int port)
        {
            this.Port = port;
            return this;
        }

        /// <summary>
        /// 设置监听网卡IP
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <returns></returns>
        public FyServer SetIpAddress(IPAddress ipAddress)
        {
            this.IpAddress = ipAddress;
            return this;
        }

        /// <summary>
        /// 设置分包模式
        /// </summary>
        /// <param name="subPackageMode"></param>
        /// <returns></returns>
        public FyServer SetSubPackageMode(SubPackageMode subPackageMode)
        {
            this.SubPackageMode = subPackageMode;
            return this;
        }

        /// <summary>
        /// 设置链接连入回调
        /// </summary>
        /// <param name="callback"></param>
        /// <returns></returns>
        public FyServer SetAcceptCallback(OnAcceptHandler callback)
        {
            this.AcceptCallback = callback;
            return this;
        }

        /// <summary>
        /// 设置连接连入，其它代码都执行结束之后的回调
        /// </summary>
        /// <param name="callback"></param>
        /// <returns></returns>
        public FyServer SetAcceptedCallback(Action<FyServer, StateObjectBase> callback)
        {
            this.AcceptedCallback = callback;
            return this;
        }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        /// <param name="callback"></param>
        /// <returns></returns>
        public FyServer SetReceiveCallback(Action<FyServer, StateObjectBase, byte[]> callback)
        {
            this.ReceiveCallback = callback;
            return this;
        }

        /// <summary>
        /// 出错回调
        /// </summary>
        /// <param name="callback"></param>
        /// <returns></returns>
        public FyServer SetExceptionCallback(Action<Exception, Socket> callback)
        {
            this.ExceptionCallback = callback;
            return this;
        }

        /// <summary>
        /// 设置接收缓存大小
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <returns></returns>
        public FyServer SetReceiveBufferSize(int bufferSize)
        {
            this.ReceiveBufferSize = bufferSize;
            return this;
        }
        #endregion

        /// <summary>
        /// 执行
        /// </summary>
        /// <param name="service"></param>
        public void Execute(IAssistantServiceContext assistantServiceContext, IAssistantService service)
        {
            try
            {
                //创建监听
                Task task = new Task(new Action(() =>
                {
                    IPEndPoint localEndPoint = new IPEndPoint(IpAddress, Port);
                    Socket listener = new Socket(this.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                    listener.Bind(localEndPoint);
                    listener.Listen(int.MaxValue);

                    while (true)
                    {
                        _allDone.Reset();
                        //Console.WriteLine("等待连接...");

                        listener.BeginAccept(MyAcceptCallback, listener);
                        _allDone.WaitOne();
                    }
                }));
                task.Start();
            }
            catch (Exception ex)
            {
                ExceptionCallback?.Invoke(ex, null);
            }
        }

        /// <summary>
        /// 客户端连入
        /// </summary>
        /// <param name="ar"></param>
        internal void MyAcceptCallback(IAsyncResult ar)
        {
            Socket handler = null;
            try
            {
                _allDone.Set();
                Socket listener = (Socket)ar.AsyncState;
                handler = listener.EndAccept(ar);

                StateObjectBase stateObject = null;
                if (AcceptCallback != null)
                    stateObject = AcceptCallback(this, handler);
                else
                    stateObject = new StateObjectBase(ReceiveBufferSize);

                stateObject.WorkSocket = handler;
                handler.BeginReceive(stateObject.Buffer, 0, stateObject.BufferSize, 0, MyReadCallback, stateObject);

                AcceptedCallback?.Invoke(this, stateObject);
            }
            catch (Exception ex)
            {
                ExceptionCallback?.Invoke(ex, handler);
            }
        }

        /// <summary>
        /// 数据接收回调
        /// </summary>
        /// <param name="ar"></param>
        internal void MyReadCallback(IAsyncResult ar)
        {
            Socket handler = null;
            try
            {
                StateObjectBase stateObject = (StateObjectBase)ar.AsyncState;
                stateObject.LastReceivedTime = DateTime.Now;
                handler = stateObject.WorkSocket;
                int bytesRead = handler.EndReceive(ar);

                if (bytesRead > 0)
                {
                    List<byte[]> segments = null;
                    switch (this.SubPackageMode)
                    {
                        case SubPackageMode.None:
                            //不分包
                            segments = new List<byte[]>();
                            byte[] bytes = new byte[bytesRead];
                            for (int i = 0; i < bytesRead; i++)
                                bytes[i] = stateObject.Buffer[i];
                            segments.Add(bytes);
                            break;
                        case SubPackageMode.InnerMode:
                            //按照关键字节分割
                            segments = SubPackingWithInnerMode.SubPacking(this.EndFlag, stateObject);
                            break;
                        case SubPackageMode.FyModeV1:
                            //按照第一个版本的内部规则分割
                            segments = SubPackingWithFyModeV1.SubPacking(bytesRead, stateObject);
                            break;
                        case SubPackageMode.CustomMode:
                            //自定义分割
                            segments = PackingFun(bytesRead, stateObject);
                            break;
                    }

                    handler.BeginReceive(stateObject.Buffer, 0, stateObject.BufferSize, 0, MyReadCallback, stateObject);

                    //以下代码可能比较耗时，放最后
                    if (ReceiveCallback != null && segments != null)
                    {
                        for (int i = 0; i < segments.Count; i++)
                        {
                            ReceiveCallback.Invoke(this, stateObject, segments[i]);
                        }
                    }
                }
                else
                {
                    //暂时就认为收到0字节数据就是断开了连接吧，等后面再看效果，不行的话就加上下方注释掉的代码再试试
                    //if(handler.Poll(10000, SelectMode.SelectRead))
                    //{
                    //    ExceptionCallback?.Invoke(new Exception("客户端断开了连接~"), handler);
                    //}
                    //ExceptionCallback?.Invoke(new Exception("客户端断开了连接~"), handler);
                }
            }
            catch (Exception ex)
            {
                ExceptionCallback?.Invoke(ex, handler);
            }
        }
    }
}
